Bazodanowa pomoc naukowa

Wersja 1.4 (po-kolokwium ediszyn)

To jest pomoc naukowa z baz danych. Obejmuje materiał potrzebny... wiadomo do czego :) czyli Javę i XML. Może tylko podpowiedzieć rzeczy których ewidetnie nie sposób zakuć na pamięć, ale nie zastąpi zrozumienia tematu i ogólnej znajomości materiału.

Najważniejszy link: iSQL *Plus.

Materiał wcześniejszy (SQL i takie tam) - Ściągaweczka bazodanowa (format PDF):

Spis treści:

I. Java

Materiały

Nasza Java w laboratorium jest w wersji 1.4.2_09. Dokumentacja do jej biblioteki standardowej jest TUTAJ.

TNS do naszej bazy danych to: jdbc:oracle:thin:@oracle.icis.pcz.pl:1521:iits

Jak pisać w Javie?

  1. Piszemy kod Javy. Jedna klasa musi być publiczna.
  2. Zapisujemy ten kod do pliku o takiej samej nazwie jak nazywa się ta klasa i rozszerzeniu java.
  3. Kompilacja: javac Plik.java. Powstaje plik o takiej samej nazwie i rozszerzeniu class.
  4. Uruchomienie: java Plik. Już bez rozszerzenia, ale wiadomo że chodzi o plik class.

Jak ćwiczyć w domu?

Da się poćwiczyć Javę i Oracle w domu!

Przykład 1

import java.sql.*;
import java.io.*;

class Java01
{
  public static void main(String args[])
    throws SQLException, IOException
  {
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    System.out.println("Prosze podac");
    String user;
    String password;
    String database;
    user = readEntry("uzytkownik = ");
    int slash_index = user.indexOf('/');
    if (slash_index != -1)
    {
      password = user.substring(slash_index+1);
      user = user.substring(0, slash_index);
    }
    else
      password = readEntry("haslo = ");
    database = readEntry("pelny TNS = ");
    System.out.flush();
    System.out.println("Bedzie polaczenie");
    Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@" +
    database, user, password);
    Statement stmt = conn.createStatement();
    ResultSet rset = stmt.executeQuery("select sysdate from dual");
    while (rset.next())
    {
      System.out.println(rset.getString(1));
    }
    rset.close();
    stmt.close();
    conn.close();
  }
  
  static String readEntry(String prompt)
  {
    try
    {
      StringBuffer buffer = new StringBuffer();
      System.out.print(prompt);
      System.out.flush();
      int c = System.in.read();
      while (c != '\n' && c != -1)
      {
        buffer.append((char)c);
  c = System.in.read();
      }
      return buffer.toString().trim();
    }
    catch(IOException e)
    {
      return "";
    }
  }
}

Co się tu dzieje?

Ogólna procedura do tych bazodanowych spraw wygląda następująco:

Przykład 2

import java.sql.*;
import java.io.*;

class Java02
{
  public static void main(String args[])
    throws SQLException, IOException
  {
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    System.out.println("Prosze podac");
    String user;
    String password;
    String database;
    String zapytanie;
    
    user = readEntry("Login (or Login/Password): ");
    int slash_index = user.indexOf('/');
    if (slash_index != -1)
    {
      password = user.substring(slash_index+1);
      user = user.substring(0, slash_index);
    }
    else
      password = readEntry("Password: ");
    database = "oracle.icis.pcz.pl:1521:iits";
    System.out.flush();
    System.out.println("Connecting...");
    Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@" +
    database, user, password);
    System.out.println("Connected.");
    Statement stmt = conn.createStatement();
    
    zapytanie = readEntry("Query: ");
    
    int ilosc_kolumn = 1;
    String wynik ="";
    
    ilosc_kolumn = Integer.parseInt(readEntry("Ilosc kolumn: "));  
            
    ResultSet rset = stmt.executeQuery(zapytanie);
    while (rset.next())
    {
    
      for (int i = 0; i< ilosc_kolumn; i++)
        {
    wynik += rset.getString(i+1) + "  |  ";
  }
    
        System.out.println(wynik);
  wynik = "";
    }
    rset.close();
    stmt.close();
    conn.close();
  }
  
  static String readEntry(String prompt)
  {
    try
    {
      StringBuffer buffer = new StringBuffer();
      System.out.print(prompt);
      System.out.flush();
      int c = System.in.read();
      while (c != '\n' && c != -1)
      {
        buffer.append((char)c);
  c = System.in.read();
      }
      return buffer.toString().trim();
    }
    catch(IOException e)
    {
      return "";
    }
  }
}

Tutaj, oprócz drobnych ulepszeń, na uwagę zasługuje niezwykła elastyczność tego programu :) Otóż program pyta użytkownika o zapytanie oraz o liczbę kolumn wyniku a następnie wypisuje wyniki tego zapytania.

Przykład 3

import java.sql.*;
import java.io.*;
import java.util.*;

class Java03  
{
static public class Struktura
{
  public String S;
  public int I;
  
  public Struktura(String aS, int aI)
  {
    S = aS;
    I = aI;
  }
}

  public static void main(String args[])
    throws SQLException, IOException
  {
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    System.out.println("Prosze podac");
    String user;
    String password;
    String database;
    String zapytanie;
    
    user = readEntry("Login (or Login/Password): ");
    int slash_index = user.indexOf('/');
    if (slash_index != -1)
    {
      password = user.substring(slash_index+1);
      user = user.substring(0, slash_index);
    }
    else
      password = readEntry("Password: ");
    database = "oracle.icis.pcz.pl:1521:iits";
    System.out.flush();
    System.out.println("Connecting...");
    Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@" +
    database, user, password);
    System.out.println("Connected.");
    Statement stmt = conn.createStatement();
    
    String Tabela = readEntry("Tabela: ");
    String Atrybut = readEntry("Atrybut: ");
    
    zapytanie = "select " + Atrybut + ", count(*) from " + Tabela + " group by "
    + Atrybut;
    
    java.util.ArrayList V = new java.util.ArrayList();
    
    ResultSet rset = stmt.executeQuery(zapytanie);
    while (rset.next())
    {
      V.add((Object)(new Struktura(rset.getString(1),
      Integer.parseInt(rset.getString(2)))));
    }
    
    // Policz sume
    int Suma = 0;
    for (int j = 0; j < V.size(); j++)
    {
      Suma += ((Struktura)(V.get(j))).I;
    }
    
    // Wypisz wyniki
    for (int i =0; i < V.size(); i++)
    {
      System.out.print(((Struktura)(V.get(i))).S + "   |   ");
      System.out.print(((Struktura)(V.get(i))).I + "   |   ");
      System.out.print((((Struktura)(V.get(i))).I*100/Suma) + " %");
      System.out.println("");
    }
    
    rset.close();
    stmt.close();
    conn.close();
  }
  
  static String readEntry(String prompt)
  {
    try
    {
      StringBuffer buffer = new StringBuffer();
      System.out.print(prompt);
      System.out.flush();
      int c = System.in.read();
      while (c != '\n' && c != -1)
      {
        buffer.append((char)c);
        c = System.in.read();
      }
      return buffer.toString().trim();
    }
    catch(IOException e)
    {
      return "";
    }
  }
}

Ten bardzo już zaawansowany przykład każe podać nazwę tabeli i wybranego pola tej tabeli, a następnie pokazuje różne wartości tego pola, liczbę rekordów które taką wartość mają i ich udział procentowy. Na przykład możemy sobie zażyczyć tabelę Student i pole Rok, a zobaczymy, ilu studentów i jaki procent z tych którzy są w bazie chodzi na który rok.

Zwróć uwagę, że tego nie da się zrobić samym SQL-em (jednym zapytaniem), bo żeby policzyć procent, trzeba znać sumę wszystkich liczb. Stąd potrzeba spamiętywania tego w ArrayList. Prześledź, jak to zostało zrobione i postaraj się zrozumieć jak działa.

Przykład własny

import java.sql.*;
import java.io.*;

class Test01
{
  public static void main(String args[])
  {
    try
    {
      DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
      String Login = "adasaw";
      String Password = "st";
      String ConnectionString = "jdbc:oracle:thin:@oracle.icis.pcz.pl:1521:iits";
      System.out.println("Connecting...");
      Connection conn = DriverManager.getConnection(ConnectionString, Login, Password);
      System.out.println("Connected.");
      
      RobZadanie(conn);
      
      conn.close();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
    
  private static String ReadEntry(String prompt)
  {
    try
    {
      StringBuffer buffer = new StringBuffer();
      if (prompt != "")
      {
        System.out.print(prompt);
        System.out.flush();
      }
      int c = System.in.read();
      while (c != '\n' && c != -1)
      {
        buffer.append((char)c);
        c = System.in.read();
      }
      return buffer.toString().trim();
    }
    catch(IOException e)
    {
      return "";
    }
  }
  
  private static void RobZadanie(Connection conn)
  {
    String s = ReadEntry("Podaj cokolwiek: ");
    System.out.println("Podales: " + s);
  }
}

Ogólnie o SQL w Java

SELECT wykonuje się tak:

Statement stmt = conn.createStatement();
ResultSet rset = stmt.executeQuery("SELECT ...");
while (rset.next())
{
  String s = rset.getString(1);
  // String s = rset.getString("nazwisko");
  int i = rset.getInt(1);
  // int i = rset.getInt("rok");
}

ResultSet to zwrócona tabelka z wynikami. Można ją sobie wyobrazić jako kursor który chodzi po wynikach zapytania. Przechodzimy kolejne rekordy i z aktualnego rekordu zczytujemy wybrane pola.

INSERT, UPDATE, DELETE itp. (zapytania DML i DDL) wykonuje się tak:

Statement stmt = conn.createStatement();
int LiczbaAffectedWierszy = stmt.executeUpdate("INSERT bleble...");

Co z NULL-ami?

int Wiek = rset.getInt("wiek");
if (!rset.wasNull())
  // użyj Wiek
else
  // wiek nieznany

Zapytania preinterpretowane

PreparedStatement pstmt = conn.prepareStatement(
  "update pracownicy set pensja = ?");

pstmt.setInt(1, 2530);
pstmt.executeUpdate();
// Tu wykonało się: update pracownicy set pensja = 2530

pstmt.setNull(1, Types.DOUBLE);
pstmt.executeUpdate();
// Tu wykonało się: update pracownicy set pensja = null

Przykład:

private static void RobZadanie(Connection conn)
  throws Exception
{
  PreparedStatement pstmt = conn.prepareStatement("SELECT * from student where nazwisko like ?");

  String nazwisko = ReadEntry("Podaj nazwisko: ");
  pstmt.setString(1, nazwisko);
  ResultSet rset = pstmt.executeQuery();

  while (rset.next())
  {
    ResultSetMetaData r = rset.getMetaData();  
    int ilosc = r.getColumnCount();
    
    for (int i=0; i < ilosc; i++)
    {
      String name = r.getColumnName(i+1);
      
      String s = rset.getString(i+1);
      System.out.print(name+ ":" +s+"|");
    }
    System.out.print("\n");
  }
  
  rset.close();
  pstmt.close();
}

Dane binarne

Oto fragment, który (podobno) zapisze łańcuch do pliku:

String str = "...";
byte buf[] = str.getBytes();
OutputStream plik = new FileOutputStream("Wyniki.txt");
plik.write(buf);
plik.close();

Dane binarne obrazka z pliku do BLOB-a w bazie danych (czy czegoś takiego):

File plik = new File("felix.gif");
InputStream in = new FileInputStream(plik);

PreparedStatement pstmt = conn.prepareStatement(
  "insert into Obrazy values (?, ?)");
pstmt.setBinaryStream(1, in, (int)file.length());
pstmt.setString(2, "FELIX");
pstmt.execute(); // a nie executeUpdate???
pstmt.close();

W drugą stronę - binarne dane z bazy danych do pliku:

ResultSet rset = stmt.executeQuery(
  "select video from filmy where tytul='Fan'");
if (rset.next())
{
  InputStream aviStream = rs.getBinaryStream(1);
  try
  {
    FileOutputStream f = new FileOutputStream("fan.avi");
    int chunk;
    while ((chunk = aviStream.read()) != -1)
      f.write(chunk);
    }
    catch (Exception e) { }
    finally { if (f != null) f.close(); }
  }
}

SPRAWDZONY przykład - upload i download plików z dysku do bazy danych. Tabela w SQL:

create table Obrazki (
  Nazwa varchar2(32),
  Dane blob
);

Program w Javie:

private static void RobZadanie(Connection conn)
  throws Exception
{
  for (;;)
  {
    System.out.println("Co chcesz zrobic?");
    System.out.println("1 - Upload pliku");
    System.out.println("2 - Download pliku");
    System.out.println("3 - Wyjscie");

    int Odpowiedz = Integer.parseInt(ReadEntry("> "));
    System.out.println("");

    if (Odpowiedz == 1)
    {
      String NazwaPliku = ReadEntry("Podaj nazwe istniejacego pliku na swoim dysku: ");
      String Nazwa = ReadEntry("Podaj nazwe dla pliku w bazie danych: ");
      
      System.out.println("Otwieranie pliku...");
      File Plik = new File(NazwaPliku);
      InputStream Strumien = new FileInputStream(Plik);
      System.out.println("Wysylanie...");
      PreparedStatement pstmt = conn.prepareStatement("insert into Obrazki values (?, ?)");
      pstmt.setString(1, Nazwa);
      pstmt.setBinaryStream(2, Strumien, (int)Plik.length());
      pstmt.executeUpdate();
      pstmt.close();
      
      System.out.println("Gotowe.");
      System.out.println("");
    }
    else if (Odpowiedz == 2)
    {
      String Nazwa = ReadEntry("Podaj nazwe istniejacego pliku w bazie danych: ");
      String NazwaPliku = ReadEntry("Podaj nazwe dla pliku na swoim dysku: ");

      System.out.println("Pobieranie...");
      PreparedStatement pstmt = conn.prepareStatement("select Dane from Obrazki where Nazwa=?");
      pstmt.setString(1, Nazwa);
      ResultSet rset = pstmt.executeQuery();
      
      if (rset.next())
      {
        System.out.println("Zapisywanie do pliku...");
        InputStream Strumien = rset.getBinaryStream(1);
        FileOutputStream Plik = new FileOutputStream(NazwaPliku);

        int Bajt;
        while ((Bajt = Strumien.read()) != -1)
          Plik.write(Bajt);
        
        Plik.close();
      }
      else
        System.out.println("Nie znaleziono.");
      
      rset.close();
      pstmt.close();

      System.out.println("Gotowe.");
      System.out.println("");
    }
    else if (Odpowiedz == 3)
      break;
  }
}

Metadane

Obiekty klas DatabaseMetaData i ResultSetMetaData dostarczają niedostępnych w inny sposób, cecnnych informacji takich jak liczba kolumn zwróconego wyniku, nazwy i typy tych kolumn. Obiekty te można otrzymać za pomocą metod getMetaData odpowiednio w klasach Connection i ResultSet.

DZIAŁAJĄCY przykład:

private static void RobZadanie(Connection conn)
  throws Exception
{
  Statement stmt = conn.createStatement();
  ResultSet rset = stmt.executeQuery("SELECT * from student where nazwisko like 'KOWALSKI'");
  while (rset.next())
  {
    ResultSetMetaData r = rset.getMetaData();  
    int ilosc = r.getColumnCount();
    
    for (int i=0; i < ilosc; i++)
    {
      String name = r.getColumnName(i+1);
      
      String s = rset.getString(i+1);
      System.out.print(name+ ":" +s+"|");
    }
    System.out.print("\n");
  }
  rset.close();
  stmt.close();
}

Transakcje

Przetwarzanie transakcyjne zależy od właściwości autoCommit obiektu Connection. Domyślnie jest true - każda operacja jest automatycznie zatwierdzana. Do zmiany służy metoda setAutoCommit(). Jeśli przestawimy na false, to możemy używać metod commit() i rollback().

Connection conn = DriverManager.getConnection(...);
conn.setAutoCommit(false);
// cośtam robię...
// jeśli jest OK
  conn.commit();
// a jeśli nie
  conn.rollback();

Poziomy izolacyjności transakcji są takie: TRANSACTION_NONE, TRANSACTION_READ_UNCOMMITED, TRANSACTION_READ_COMMITED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE. Zapisujemy i odczytujemy tak:

conn.setTransactionIsolation(TRANSACTION_READ_COMMITED);
int Level = conn.getTransactionLevel();

Procedury składowane

W PL/SQL jest procedura:

procedure podwyzka(komu in number, o_ile in number)
...

W Javie używamy tego tak:

CallableStatement cs = conn.prepareCall(
  " { call podwyzka(?, ?) } ");
cs.setInt(1, 1520);
cs.setDouble(2, 450);
cs.executeUpdate();

To była procedura. Teraz funkcja. Funkcja jak to funkcja, może coś zwracać. Poza tym załóżmy, że ma dwa parametry, a ten drugi jest wyjściowy, czyli przez niego też coś zwraca. Wtedy to leci tak:

CallableStatement cs = conn.preapreCall(
  " { ? = call oblicz(?, ?) } ");
cs.registerOutputParameter(1, Types.NUMERIC);
cs.setInt(2, 1600);
cs.registerOutputParameter(3, Types.VARCHAR2);
cs.executeUpdate();
// Teraz można używać: cs.getDouble(1), cs.getString(3)

Podobno można też za pomocą getInt(2) odczytać parametr który był wejściowy (sami go podaliśmy), ale też trzebaby go zarejestrować jak te wyjściowe.

Aktualizowanie wsadowe

To jeszcze wyższy poziom optymalizacji niż zapytania preinterpretowane. Używa się tutaj też PreparedStatement, ale ustawia się wartości i dodaje taki stan do wykonania, potem przestawia na inne wartości, znowu dodaje itd., a na koniec jeden raz wykonuje to wszystko. Zwracana jest tablica z liczbami wierszy zmodyfikowanych w kolejnych zapytaniach.

PreparedStatement pstmt = conn.prepareStatement(
  "update pracownicy set placa_pod = placa_pod + ? where numer = ?");
  
stmt.setFloat(1, new Float(0.9)); // ten potworek nie wiem skąd jest
stmt.setInt(2, 1500);
stmt.addBatch();

stmt.setFloat(1m new Float(1.5));
stmt.setInt(2, 1200);
stmt.addBatch();

int[] zmiany = stmt.executeBatch();
System.out.println("Zmodyfikowano " + zmiany[0] + "," + zmiany[1]);

Przewijanie wyników

ResultSet można przechodzić bardziej swobodnie niż tylko za pomocą next(). Trzeba tylko utworzyć Statement ze specjalnymi flagami:

Statement stmt = conn.createStatement(
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_READ_ONLY);

I zaraz po dostaniu w łapy ResultSeta można mu wywoływać metody takie jak: absolute(i), relative(i), first(), last().

Modyfikowanie wyników

Jeśli wyobrazić sobie ResultSet jako taki dziwny kursor do przechodzenia po danych w bazie zamiast kopii tych danych zwróconych w tabelce, to da się też wyobrazić sobie modyfikowanie tych danych (oczywiście tylko jeśli dane pole nie jest wynikiem działania jakiejś funkcji albo agregacji). Robi się to tak:

Statement stmt = conn.createStatement(
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery(
  "select numer, nazwisko, placa_pod from pracownicy");

// Zmienimy ostatni rekord
rs.last();
rs.updateString(2, rs.getString(2) + " BIS");
rs.updateFloat("PLACA_POD", new Float(rs.getFloat(3) * 1.1));
rs.updateRow();

// A teraz, też za pomocą tego ResultSeta, wstawimy nowy rekord!
rs.moveToInsertRow();
rs.updateInt(1, 1200);
rs.updateString(2, "Rutkowski");
rs.updateFloat(3, new Float(2750));
rs.insertRow();

Obsługa własnych typów

Da się podobno obsługiwać własne typy utworzone za pomocą "namiastki obiektowości" Oracle w Javie. Ktoś w ogóle sprawdzał czy to działa? Ktoś to w ogóle zdążył przypisać na wykładzie w całości???

Metoda 1:

public class PersonObject
{
  public static void main(Strings args[])
    throws Exception
  {
    ...
    stmt.execute("create type ADDRESS as object (
    street varchar2(30),
    num number)");
    ...
    stmt.execute("insert into people values(101, PERSON('Greg', ADDRESS('Van Nesr', 345)))");
    ...
    ResultSet rs = stmt.executeQuery("select * from people");
    showResultSet(rs);
    rs.close();
    Object[] address_attributes = new Object[2];
    address_attributes[0] = "Mission";
    address_attributes[1] = new BigDecimal(346);
    StructDescriptor personDesc = StructDescriptor(
    ...

  public static void ShowResult(ResultSet rs)
  {
    while (rs.next())
    {
      int empno = rs.getInt(1);
      STRUCT person_struct = (STRUCT)rs.getObject(2);
      Object person_attrs[] = person_struct.getAttributes();
      System.out.println("person name: " + (String)person_attrs[0]);
      STRUCT address = (STRUCT)person_attrs[1];
      System.out.println("person address: ");
      Object address_attrs[] = address.getAttributes();
      System.out.println("Street: " + (String)address_attrs[0]);
      System.out.println("Number: " + )BigDecimal)address_attrs[1]).intValue());
      System.out.println();
    }
  }
}

Aaaaa, już chyba rozumiem. Java domyślnie zwraca dane typu własny typ jako typu Struct (jest taka klasa w module java.sql, pisana jednak z dużej litery a nie dużymi literami), a ta klasa ma metodę getAttributes(). Ta metoda zwraca tablicę Object[] i tam pewnie siedzą poszczególne pola tej wartości i można je sobie przerzutować na właściwy im typ. Ten kod powyżej jest taki zamotany pewnie dlatego, że tam jest jedna struktura w drugiej strukturze.

Sprawdzony przykład (nie działa do końca) - SQL:

create or replace type MojTypSql as object (
  PoleString varchar2(32),
  PoleInt number
);

create table MojaTabela (
  Id number,
  Obiekt MojTypSql
);

insert into MojaTabela values(1, MojTypSql('String 1', 11));

Kod Java:

private static void RobZadanie(Connection conn)
  throws Exception
{
  Statement stmt = conn.createStatement();
  ResultSet rset = stmt.executeQuery("select Id, Obiekt from MojaTabela");
  
  while (rset.next())
  {
    int Id = rset.getInt(1);
    Struct Struktura = (Struct)rset.getObject(2);
    Object[] Pola = Struktura.getAttributes();
    System.out.println("Id="+Id+", poleString="+Pola[0]+", poleInt="+Pola[1]);
  }
  
  rset.close();
  stmt.close();
}

Metoda 2 - przez mapowanie

public class Attributes implements SQLData
{
  public String Street;
  public int house;
  private String sql_type;
  public String getSQLTypeName() { return sql_type; }
  public void readSQL(SQLInput Stream, String type)
    throws SQLException
  {
    sql_type = type;
    street = stream.readString();
    house = stream.readInt();
  }
  public void writeSQL(SQLOutput stream)
    throws SQLException
  ...

java.util.Map map = conn.getTypeMap();
map.put("ADDRESS", Class.forName("Address"));

...
rset = stmt.executeQuery(...
...
Address h = (Address)rset.getObject(2);

PreparedStatement ps = conn.prepareStatement(
  "update people set home=? where name='CLARK'");
h.street = "Oak Street";
h.house = 99;
ps.setObject(1, h);
ps.executeQuery();
ps.close();

Tutaj wygląda na to, że definiujemy sobie własną klasę odpowiadającą własnemu typowi w Oracle, która implementuje interfejs SQLData i posiada metody: getSQLTypeName(), readSQL(), writeSQL(). W readSQL() dostaje nazwę typu i obiekt klasy SQLInput (która, jak widać w jej dokumentacji z modułu java.sql, posiada jedynie proste metody wczytujące takie jak readInt, readBoolean itp.). Ma tam za zadanie zapamiętać sobie ten typ i wczytać swoje pola z tego "strumienia". Metoda writeSQL() ma się zapisać do przekazanego "strumienia" SQLOutput. Kiedy już klasa to potrafi, pozostaje tylko zarejestrować nasz typ w jakiejśtam mapie typów należącej do połączenia z bazą danych i już można z wyników zapytania rzutować sobie obiekty na naszą klasę oraz wpisywać takie obiekty do bazy danych (za pomocą odpowiednio getObject() i setObject()).

Sprwadziłem tą metodę - nie działa :P

private static void RobZadanie(Connection conn)
  throws Exception
{
  conn.getTypeMap().put("MojaKlasa", Class.forName("MojaKlasa"));
  
  System.out.println("== INSERT ==");
  {
    MojaKlasa Obiekt = new MojaKlasa();
    Obiekt.poleString = ReadEntry("Podaj String: ");
    Obiekt.poleInt = Integer.parseInt(ReadEntry("Podaj int: "));
    
    PreparedStatement pstmt = conn.prepareStatement(
      "insert into Klasa values (?)");
    pstmt.setObject(1, Obiekt);
    pstmt.executeUpdate();
    pstmt.close();
    System.out.println("Gotowe.");
  }
}

Błąd: java.sql.SQLException: Nie uda?o si? utworzy? deskryptora: Invalid arguments

Druga próba:

create or replace type MojTypSql as object (
  PoleString varchar2(32),
  PoleInt number
);

create table MojaTabela (
  Id number,
  Obiekt MojTypSql
);

insert into MojaTabela values(1, MojTypSql('String 1', 11));
class MojTyp implements SQLData
{
  public String PoleString;
  public int PoleInt;
  
  private String SqlType;
  
  public String getSQLTypeName() { return SqlType; }
  public void readSQL(SQLInput Stream, String Type)
    throws SQLException
  {
    SqlType = Type;
    PoleString = Stream.readString();
    PoleInt = Stream.readInt();
  }
  public void writeSQL(SQLOutput Stream)
    throws SQLException
  {
    Stream.writeString(PoleString);
    Stream.writeInt(PoleInt);
  }
}

private static void RobZadanie(Connection conn)
  throws Exception
{
  conn.getTypeMap().put("MOJTYPSQL", Class.forName("MojTyp"));
  
  PreparedStatement pstmt = conn.prepareStatement(
    "select Id, Obiekt from MojaTabela");
  ResultSet rset = pstmt.executeQuery();
  
  while (rset.next())
  {
    int Id = rset.getInt(1);
    MojTyp Obiekt = (MojTyp)rset.getObject(2);
  }
  
  rset.close();
  pstmt.close();
}

Wynik: java.sql.SQLException: Niespˇjne typy obiektˇw SQL i obiektˇw Javy: IllegalAcces sException: Class oracle.sql.STRUCT can not access a member of class MojTyp with modifiers ""

Metoda 3 - Obejście

To właściwie nie jest metoda, tylko obejście. Polega na tym, żeby w SQL-u był własny typ, ale w Javie ani podczas INSERT-ów ani SELECT-ów z niego nie korzystać tylko specyfikować lub wybierać bezpośrednio poszczególne pola.

To jedyna działająca metoda. Mimo tego niezalecana ze względu na "nie o to chodziło", więc don't try this at home itepe idete, a przed użyciem skonsultuj się z prowadzącym, z lekarzem lub z farmaceutą :)

SQL:

create or replace type MojTyp as object (
  PoleString varchar2(32),
  PoleInt number
);

create table MojaTabela of MojTyp;

Java:

private static void RobZadanie(Connection conn)
  throws Exception
{
  System.out.println("== INSERT ==");
  {
    PreparedStatement pstmt = conn.prepareStatement(
      "insert into MojaTabela values (?, ?)");
    String PoleString = ReadEntry("Podaj String: ");
    int PoleInt = Integer.parseInt(ReadEntry("Podaj int: "));
    pstmt.setString(1, PoleString);
    pstmt.setInt(2, PoleInt);
    pstmt.executeUpdate();
    pstmt.close();
    System.out.println("Gotowe.");
  }
  
  System.out.println("== SELECT ==");
  {
    Statement stmt = conn.createStatement();
    ResultSet rset = stmt.executeQuery(
      "select PoleString, PoleInt from MojaTabela");
    while (rset.next())
    {
      System.out.println(
        "PoleString=" + rset.getString(1) + ", " +
        "PoleInt=" + rset.getInt(2));
    }
    rset.close();
    stmt.close();
  }
}

Kontener ArrayList

Pełna dokumentacja: TUTAJ. Klasa leży w module java.util.ArrayList.

Uwaga! Jeśli trafiłeś na dokumentację tej klasy, która pozwala na parametryzowanie ją typem, np. ArrayList<int>, jak szablony w C++, to trafiłeś w złe miejsce - to jest dostępne tylko w nowej wersji Javy, a w laboratorium jest stara i tam takie bajery nie działają.

Ten stary ArrayList natomiast przechowuje obiekty klasy Object, która jak wiadomo jest klasą bazową wszystkich klas w Javie. Wszystkich z wyjątkiem typów atomowych, które inaczej niż w .NET-cie klasami nie są. Dlatego celem zapamiętania w ArrayList na przykład int-ów trzeba je zapisać jako obiekty klasy Integer.

Ważniejsze metody klasy ArrayList:

ArrayList()
ArrayList(int InitialCapacity)
void add(int index, Object element)
boolean add(Object o)
void clear()
boolean contains(Object element)
Object get(int index)
int indexOf(Object element)
boolean isEmpty()
Object remove(int index)
int size()
Object set(int Index, Object element)

Prosty program testujący:

import java.util.*;

public class Test
{
  public static void main(String[] Args)
  {
    ArrayList ListaIntow = new ArrayList();

    ListaIntow.add(new Integer(1));
    ListaIntow.add(new Integer(2));
    ListaIntow.add(new Integer(3));

    for (int i = 0; i < ListaIntow.size(); i++)
      System.out.println("Wartosc nr " + i + " = " + ListaIntow.get(i));
  }
}

Liczby losowe - klasa Random

Pełna dokumentacja: TUTAJ. Klasa leży w module java.util.Random.

  1. Utworzyć obiekt klasy Random.
  2. Używać jego metod nextCośtam() do generowania nowych liczb losowych.

Skrót ważniejszych metod klasy:

Random()
Random(long Seed)
boolean nextBoolean()
double nextDouble()
float nextFloat()
int nextInt()
int nextInt(int n)

Szybkie Java FAQ

Jaką składnię ma Java?

Bardzo podobną do C++. Konstrukcje językowe typu for, do, while, if-else itd. są identyczne. Podstawowe typy danych to m.in. int, float, boolean, String.

Jak się w Javie używa klas i obiektów?

Tak samo jak w C# i całym .NET. Nie ma wskaźników, nie ma obiektów automatycznych, nie ma niczego ;) każda zmienna typu jakaś klasa jest referencją do obiektu tej klasy. Trzeba obiekt utworzyć za pomocą operatora new. Przypisanie kopiuje tylko referencję. Obiektów się nie zwalnia - same się odśmiecają. Na przykład:

class Struktura
{
  String s1;
  String s2;

  public Struktura(String a1, String a2)
  {
    s1 = a1;
    s2 = a2;
  }
}

...
Struktura s = new Struktura("A", "B");
String Lancuch = s.s1;
Jak konwertować łańcuch na liczbę i odwrotnie?

Prosto. Trzeba użyć metod statycznych klasy Integer, która jest tak jakby obiektowym odzwierciedleniem typu atomowego int. Dla klas Float, Double, Boolean itd. będzie analogicznie. Są też inne metody.

String Lancuch1 = "666";
int Liczba1 = Integer.parseInt(Lancuch1);
String Lancuch2 = Integer.toString(Liczba1);

Jak się używa tablic?

O tak:

int[] Tablica = new int[10];
Tablica[0] = 0;
Tablica[1] = 1;
Tablica[2] = Tablica[0] + Tablica[1];
Czy jest jakiś kontener jak vector z STL-a z C++?

Trudno, żeby nie było :P Nazywa się tak jak w .NET-cie ArrayList. WIĘCEJ

O co chodzi z tym błędem kompilacji "unreported exception"?

Chodzi o to, że w Javie trzeba albo łapać w danej funkcji wszystkie wyjątki jakie mogą być rzucane przez funkcje które ona wywołuje (czyli blok try-catch), albo trzeba zadeklarować w nagłówku funkcji jakiego typu wyjątki mogą z niej wylecieć. Dopisanie do nagłówka, że funkcja może rzucać wszelkie wyjątki świata, załatwi sprawę. Tak samo trzeba też w każdej innej funkcji.

public static void main(String[] Args)
  throws Exception // bez tego będzie błąd...
{
  Thread.sleep(1000); // ...bo to może rzucać wyjątek
}

II. XML

Ta część jest wbrew pozorom prostsza niż Java. Mniej jest tutaj materiału. Takie wrażenie odniósł przynajmniej autor tego tutoriala :)

Na początek zabawy z XML-em w iSQL *Plus terzba zapodać komendę set long 10000;, zeby nie obcinał wyników do kilkudziesięciu znaków na rekord.

Tabele do ćwiczenia spraw XML-owych to: cv, ksiazki_xml (niezależne tabele), lp3_utwory, lp3_wykonawcy ("Lista przebojów trójki").

Typ XMLType

Jest taki typ danych w Oracle'u - XMLType. Można go używać na dwa sposoby: 1. Jako typ jednego z pól tabeli:

create table Dupa (
  id integer,
  nazwa varchar(32),
  dane xmltype
);

insert into Dupa values (1, 'Jeden', XMLType('<a>To jest XML</a>'));
insert into Dupa values (2, 'Dwa', XMLType('<b>To też jest XML</b>'));

select * from Dupa;
select dane from Dupa;

2. Jako typ całej tabeli:

create table Dupa of xmltype;

insert into Dupa values (xmltype('<a>To jest XML</a>'));
insert into Dupa values (xmltype('<b>To też jest XML</b>'));

select * from Dupa;
select value(T) from Dupa T;

Funkcje do generowania XML-a

Istnieje szereg funkcji do generowania na wyjściu XML-a ze zwykłych danych relacyjnych. W skrócie:

XMLElement(name "NAZWA", COŚTAM1, COŚTAM2, ...)
XMLElement("NAZWA", COŚTAM1, COŚTAM2, ...)
Tworzy element o podanej nazwie i podanej zawartości. Zawartością mogą być elementy, atrybuty i zwykły tekst, w dowolnej ilości i kombinacji. Zwróć uwagę, że nazwa dla elementu podawana jest w cudzysłowach, a nie apostrofach jak wszystkie zwykłe łańcuchy w Oracle SQL! Słówko "name" jest opcjonalne.
XMLAttributes(WARTOŚĆ1, WARTOŚĆ2, ...)
XMLAttributes(WARTOŚĆ1 as "NAZWA1", WARTOŚĆ2 as "NAZWA2", ...)
XMLAttributes(WARTOŚĆ1 "NAZWA1", WARTOŚĆ2 "NAZWA2", ...)
Tworzy atrybuty dla elementu w którym leży - także kilka na raz. Opcjonalnie można wyspecyfikować ich nazwy. Jeśli nie podane, jakieś zostaną wydedukowane. Słówko "as" jest opcjonalne. Uwaga! Jest też funkcja XMLAttribute, o której się nie uczyliśmy, więc jeśli popełnisz literówkę to będzie dziwny bład.
XMLForest(WARTOŚĆ1, WARTOŚĆ2, ...)
XMLForest(WARTOŚĆ1 as "NAZWA1", WARTOŚĆ2 as "NAZWA2", ...)
XMLForest(WARTOŚĆ1 "NAZWA1", WARTOŚĆ2 "NAZWA2", ...)
Tworzy "las", czyli po prostu ciąg elementów o podanych opcjonalnie nazwach i zawierających podane wartości. Słówko "as" jest opcjonalne.
XMLAgg(COŚTAM...)
Łączy dane XML-owe zwracane w osobnych rekordach wyniku w pojedynczy rekord.
XMLConcat(COŚTAM1, COŚTAM2, ...)
Łączy, po prostu skleja dwa elementy XML w jedno pole, żeby w wynikach pojawiły się razem a nie jako osobne pola.

Przykład:

select XMLElement(name "Nazwisko", nazwisko) from pracownicy;

Wynik:

<Nazwisko>KRÓL</Nazwisko>
<Nazwisko>KUKULSKI</Nazwisko>
...

Przykład:

select XMLAgg(XMLElement(name "Nazwisko", nazwisko))
from pracownicy;

Wynik:

<Nazwisko>KRÓL</Nazwisko><Nazwisko>KUKULSKI</Nazwisko><Nazwisko>WIERZBICKI</Nazwisko>...

Przykład:

select XMLElement(name "Pracownik",
  XMLAttributes(Nazwisko as "Nazwisko"),
  'Zwolniony'
)
from Pracownicy;

Wynik:

<Pracownik Nazwisko="KRÓL">Zwolniony</Pracownik>
<Pracownik Nazwisko="KUKULSKI">Zwolniony</Pracownik>
...

Przykład:

select xmlforest(
  nazwisko,
  placa as "Płaca",
  'Tak' as "Zwolniony")
from pracownicy;

Wynik:

<NAZWISKO>KRÓL</NAZWISKO><Płaca>7000</Płaca><Zwolniony>Tak</Zwolniony>
<NAZWISKO>KUKULSKI</NAZWISKO><Płaca>5000</Płaca><Zwolniony>Tak</Zwolniony>
<NAZWISKO>WIERZBICKI</NAZWISKO><Płaca>2500</Płaca><Zwolniony>Tak</Zwolniony>
...

Ten przykład jest niesamowity. Wykracza poza możliwości mojej wyobraźni ;) Otóż tutaj jakimś cudem mimo użycia grupowania udaje się użyć pola według którego nie grupujemy i na dodatek w ramach jednego rekordu wyliczają się wszystkie występujące tam wartości!

select XMLElement(name "Dzial",
  XMLAttributes(id_dzialu as "Numer"),
  XMLAgg(
    XMLElement(name "Pracownik", nazwisko)
  )
) wynik
from pracownicy
group by id_dzialu;

Wynik:

<Dzial Numer="10"><Pracownik>KRÓL</Pracownik><Pracownik>SKALSKI</Pracownik>...</Dzial>
<Dzial Numer="20"><Pracownik>MONIUSZKO</Pracownik><Pracownik>LISIECKI</Pracownik>...</Dzial>
...

To zwróci dwa osobne pola:

select
  XMLElement(name "Nazwisko", nazwisko),
  XMLElement(name "Dzial", id_dzialu)
from pracownicy;

Wynik:

<Nazwisko>KRÓL</Nazwisko>                <Dzial>10</Dzial>
<Nazwisko>KUKULSKI</Nazwisko>            <Dzial>30</Dzial>
...

A to zwróci w każdym rekordzie tylko jedno pole, w nim dwa elementy:

select XMLConcat(
  XMLElement(name "Nazwisko", nazwisko),
  XMLElement(name "Dzial", id_dzialu)
)
from pracownicy;

Wynik:

<Nazwisko>KRÓL</Nazwisko> <Dzial>10</Dzial>
<Nazwisko>KUKULSKI</Nazwisko> <Dzial>30</Dzial>
...

Można też połączyć te dwa elementy w sposób tekstowy - wyjdzie na to samo:

select
  XMLElement(name "Nazwisko", nazwisko) ||
  XMLElement(name "Dzial", id_dzialu)
from pracownicy;

Wynik:

<Nazwisko>KRÓL</Nazwisko><Dzial>10</Dzial>
<Nazwisko>KUKULSKI</Nazwisko><Dzial>30</Dzial>
...

dbms_xmlgen

To jest taki sprytny mechanizm do automatycznego generowania całości dokumentu XML z danych relacyjnych w bardzo prosty sposób.

select dbms_xmlgen.getXML('select * from pracownicy')
from dual;

Wynik:

<?xml version="1.0"?> <ROWSET>  <ROW>  <NR_AKT>8901
</NR_AKT>  <NAZWISKO>KRÓL</ NAZWISKO>  <STANOWISKO>PREZES
</STANOWISKO>  <DATA_ZATR>89/07/01</DATA_ZATR> <PLACA>
7000</PLACA>  <DOD_FUNKCYJNY>4500</DOD_FUNKCYJNY>  <PROWIZJA
>100</PROWI ZJA>  <ID_DZIALU>10</ID_DZIALU>  </ROW>  
<ROW>  <NR_AKT>9121</NR_AKT>...

Zwróć uwagę, że wybieramy z "niczego" (tabela dual). Podobno z próżnego i Salomon nie naleje... a Oracle potrafi :D Natomiast główne zapytanie jest podane jako łańcuch. Wygląda śmiesznie, ale tak to się właśnie robi.

Uwaga! Powyższe zapytanie select * from pracownicy wpisaliśmy w apostrofach. Jeśli zapytanie wyglądałoby tak: select * from pracownicy where nazwisko like 'Kowalski' trzeba apostrofy pisać podwójnie: (' select * from pracownicy where nazwisko like ''Kowalski'' ')

Jest jeszcze druga funkcja, dbms_xmlgen.GetXMLType, która różni się od powyższej tylko tym, że do wyniku nie dołącza preambuły XML, czyli tego <?xml version="1.0"?>.

Chcąc w wyniku zmienić nazwy kolumn należy po prostu nadać alias wybieranym kolumnom:

select dbms_xmlgen.getXML('select nazwisko as N from pracownicy')
from dual;

Wynik:

<?xml version="1.0"?> <ROWSET>  <ROW>  <N>KRÓL</N>  </ROW>  <ROW>  <N>KUKULSKI
</N>  </ROW>  <ROW>  <N>WIERZBICKI</N>  </ROW>  <ROW>  <N>FIKUS</N>  </ROW>
< ROW>  <N>SKALSKI</N>  </ROW>  <ROW> ...

Wstawienie wygenerowanego dokumentu XML do tabeli

create table NowaTabela of xmltype;
insert into NowaTabela
  select ...

Wypasiony przykład

Nie muszę chyba tłumaczyć, że ten przykład jest szczególnie ważny i trzeba go rozgryźć, bo "takie coś będzie", wiadomo gdzie :)

select XMLElement(name "zespoly",
  XMLAgg(
    XMLElement(name "zespol",
      XMLElement(name "nazwa", dz.nazwa),
      XMLElement(name "liczba_pracownikow", count(*)),
      XMLElement(name "pracownicy",
        (
          select XMLAgg(
            XMLElement(name "nazwisko", pp.nazwisko)
          )
          from pracownicy pp
          where pp.id_dzialu = dz.id_dzialu
        )
      )
    )
  )
)
from dzialy dz, pracownicy p
where p.id_dzialu = dz.id_dzialu
group by dz.nazwa, dz.id_dzialu;

Wynik:

<zespoly><zespol><nazwa>ZARZĽD</nazwa><liczba_pracownikow>3</liczba_pracownikow>
<pracownicy><nazwisko>KRÓL</nazwisko><nazwisko>SKALSKI</nazwisko><nazw isko>
KUKULSKI</nazwisko></pracownicy></zespol><zespol><nazwa>BADANIA</nazwa>
<liczba_pracownikow>2</liczba_pracownikow><pracownicy><nazwisko>PRUSIŃSKA
< /nazwisko><nazwisko>NAWROCKI</nazwisko></pracownicy></zespol>...

Perspektywa

NOWOŚĆ! SPRAWDZONE I DZIAŁA!

create or replace view Prac_xml_v1 of xmltype
with object id(extract(sys_nc_rowinfo$, '/PRACOWNIK/@NUMER').getNumberVal())
as select XMLElement(name "PRACOWNICY",
  XMLAgg(
    XMLElement(name "PRACOWNIK",
      XMLAttributes(nr_akt as "NUMER"),
      XMLForest(nazwisko, stanowisko, placa)
    )
  )
)
from pracownicy;

select extract(value(T), '/PRACOWNICY/PRACOWNIK[NAZWISKO="KUKULSKI"]')
from Prac_xml_v1 T;

XPath

Prosty tutorial do XPath znajdziesz TUTAJ. Tam też jest lista funkcji, operatorów, axes itp.

Co do samego XPath, przed pisaniem wyrażenia ścieżkowego warto sobie wyobrazić albo nawet rozrysować strukturę dokumentu XML-owego. Wyrażenia ścieżkowe są jak ścieżki do plików. I tak:

Warto być świadomym, że takie wyrażenie składa się z członów, z których każdy może mieć:

  1. Axis, np. parent::, ancestor::, child:: - o tym się nie uczyliśmy
  2. To o co chodzi, czyli:
  3. Predykat, czyli po prostu warunek - pisany w nawiasie kwadratowym

Poniższe trzy wyrażenia (SPRAWDZONE) są jednoznaczne:

select extract(value(T), '//ulica[../../nazwisko="Mikołaj"]') from cv T;
select extract(value(T), '//dane_osobowe[nazwisko="Mikołaj"]/adres/ulica') from cv T;
select extract(value(T), 'zbior/cv/dane_osobowe[nazwisko="Mikołaj"]/adres/ulica') from cv T;

Funkcje związane z XPath w Oracle SQL to:

existsNode(KOLUMNA, WYRAŹENIE)
Zwraca 1 lub 0 zależnie czy znajdzie choć jeden węzeł kodu XML z podanej kolumny, który pasuje do podanego wyrażenia ścieżkowego.
extract(KOLUMNA, WYRAŹENIE)
Wybiera i zwraca węzeł podany wyrażeniem ścieżkowym.
extractValue(KOLUMNA, WYRAŻENIE)
Wybiera i zwraca treść węzła podanego wyrażeniem ścieżkowym, np. sam tekst leżący w dopasowanym elemencie zamiast całego elementu.

Przykład:

select extract(value(T), 'zbior/cv/dane_osobowe/nazwisko')
from cv T;

Wynik:

<nazwisko>Mikołaj</nazwisko> <nazwisko>Buras</nazwisko> <nazwisko>Nowak</nazwisko>...

Funkcja extractValue może pracować tylko na pojedynczej wartości, więc tylko taką należy wybierać podanym wyrażeniem ścieżkowym. Przykład:

select extractValue(value(T), 'zbior/cv/dane_osobowe/nazwisko[../imie="Kot"]')
from cv T;

Wynik:

Buras

Przykład:

select existsNode(value(T), '//dane_osobowe[nazwisko="Buras"]')
from cv T;

Wynik:

1

TODO: wytłumaczyć...

Oracle XML SQL Utility (XSU) - Java API

Nie wiem co tu się dzieje, ale jest coś takiego w zeszycie ;)

import oracle.jdbc.driver.*;
import java.sql.*;
import oracle.xml.sql.query.OracleXMLQuery;
...
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
Connection conn = driverManager.getConnection("jdbc:oracle:thin...");
...
OracleXMLQuery q = new OracleXMLQuery(conn, "select * from Pracownicy");
q.setRowsetTag("pracownicy");
q.setRowTag("pracownik");
q.setEncoding("Windows-1250");
String xmlString = q.getXMLString();
System.out.println(xmlString);
q.close();

Inny skrawek kodu, który zdążyłem przepisać na wykładzie:

import oracle.jdbc.driver.*;
import java.sql.*;
import oracle.xml.sql.dml.OracleXMLSave;
...
DriverManager

Connection  ...

...
OracleXMLSave sav = new OracleXMLSave(conn, "zbiory");
sav.insertXML(sav.createURL("Zbiory_xml"));
sav.close();
...

Albo ten:

import
i
i  ...
...
DriverManager

Connection...

...
OracleXMLSave sav = new OracleXMLSave(conn, "zbiory");
String[] keyColNames = new String[1];
keyColNames[0] = "SYGNATURA";
sav.saveKeyColumnList(keyColumnNames);
sav.updateXML(sav.createURL("Zbiory_xml"));

To podobno modyfikuje dane w tabeli relacyjnej stosownie do tego co jest w podanym pliku XML.

import oracle.jdbc.driver.*;
import java.sql.*;
import oracle.xml.sql.dml.OracleXMLSave;
...
DriverManager
  ...
Connection

...
OracleXMLSave sav = new OracleXMLSave(conn, "Pracownicy");
sav.deleteXML(sav.createURL("Pracownicy_bis_xml"));
sav.close();

Funkcja UpdateXML

Funkcja UpdateXML z Oracle SQL pozwala nam zmodyfikować jakąś wartość wewnątrz dokumentu XML-owego przechowywanego w bazie danych, o tak:

update cv c
set c = updatexml(
  value(c),
  '//dane_osobowe[nazwisko="Kowalska"]/data_urodzenia/text()',
  '1999'
);

XML z konsoli systemowej

Można też XML-ować sobie bez iSQL *Plus-a, wystarczy Alt+X i linuksowa konsola!

java OracleXML getXML -user LOGIN/HASŁO
  -conn "jdbc:oracle:thin:@oracle.icis.pcz.pl:1521:iits"
  "select * from pracownicy" > plik.xml

Kolokwium

Na zakończenie, spis materiału na kolokwium:

Powodzenia!